package stud.mpgame.network.service.ping;

import org.apache.mina.core.session.IoSession;

import org.slf4j.LoggerFactory;

import stud.mpgame.network.IMessageReceivedListener;
import stud.mpgame.network.Message;
import stud.mpgame.network.NetworkMonitor;

import stud.mpgame.network.util.MessageFilter;
import stud.mpgame.network.util.ResponseUserObject;

import java.util.List;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author f.pavel91@gmail.com
 * @version 1.4
 */
public class PingService implements IMessageReceivedListener {

    private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PingService.class);

    private static final int INITIAL_DELAY = 100;
    public static final int WRONG_PING_AMOUNT = 10;

    private final List< IoSession > sessions = new ArrayList< IoSession >();

    private boolean notifyClient;

    private int stabilizationNumber;

    public PingService(NetworkMonitor networkMonitor, int tickTime, int stabilizationTime, boolean notifyClient) {

        //register listeners
        MessageFilter messageFilter = new MessageFilter(PingRequestMessageDescription.getInstance());
        networkMonitor.getResponseMessageDispatcher().addMessageReceivedListener(messageFilter, this);

        this.notifyClient = notifyClient;
        this.stabilizationNumber = stabilizationTime / tickTime;

        Timer timer = new Timer();
        timer.schedule(new PingRequestSenderTask(), INITIAL_DELAY, tickTime);
    }

    public void addSession(IoSession session) {
        sessions.add(session);
        session.setAttribute("pingAmount", 0);
    }

    List< IoSession > getSessions() {
        return sessions;
    }

    public void removeSession(IoSession session) {
        synchronized ( sessions ) {
            session.removeAttribute("ping");
            sessions.remove(session);
        }
    }

    public int getPing(IoSession session) {
        Object ping = session.getAttribute("ping");
        if(ping == null) {
            return -1;//throw new Exception("Ping is not defined");
        }
        return (Integer) ping;
    }

    private class PingRequestSenderTask extends TimerTask {

        public void run() {
            try {
                Message message = new Message(sessions, PingRequestMessageDescription.getInstance());
                message.send();
                //for(IoSession session : sessions)
                  // LOGGER.info(session + " ping: " + getPing(session));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public void messageReceived(Message message) {
        IoSession session = message.getFirstSession();

        int pingAmount = (Integer)session.getAttribute("pingAmount");
        session.setAttribute("pingAmount", pingAmount + 1);

        if(pingAmount < WRONG_PING_AMOUNT)
            return;

        if(pingAmount == WRONG_PING_AMOUNT) {
            int ping = calculateCurrentPing(message);
            session.setAttribute("ping", ping);
            return;
        }

        int ping = calculateAveragePing(message);
        session.setAttribute("ping", ping);

        if(notifyClient) {
            Message notificationMessage = new Message(session, PingNotificationMessageDescription.getInstance());

            PingNotificationUserObject pingNotificationUserObject = new PingNotificationUserObject(ping);
            notificationMessage.putUserObject(pingNotificationUserObject);

            notificationMessage.send();
        }

    }

    private int calculateAveragePing(Message message) {
        int currentPing = calculateCurrentPing(message);
        int currentAveragePing = getPing(message.getFirstSession());
        //TODO very slowly, need pre-calculation
        return (currentAveragePing * stabilizationNumber + currentPing) / (stabilizationNumber + 1);

    }

    private int calculateCurrentPing(Message message) {
        ResponseUserObject pingRequest = (ResponseUserObject) message.getUserObject(ResponseUserObject.class);

        int pingRequestSentTime = pingRequest.getSentTime(); // server sent;
        int pingRequestReceivedTime = pingRequest.getReceivedTime(); // client received
        int pingResponseSentTime = message.getSentTime(); // client sent
        int pingResponseReceivedTime = message.getReceivedTime(); //server received

        int serverTimeDifferences = pingResponseReceivedTime - pingRequestSentTime;
        int clientTimeDifferences = pingResponseSentTime - pingRequestReceivedTime;

        return ( serverTimeDifferences - clientTimeDifferences ) / 2;
    }

}
